package com.zhuss.lock;

import cn.hutool.core.collection.ConcurrentHashSet;
import cn.hutool.extra.spring.SpringUtil;
import com.zhuss.appconfig.RedisProcessor;
import com.zhuss.exception.AabException;
import com.zhuss.exception.ExceptionCode;
import com.zhuss.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
 * 锁 统一处理切面
 *
 * @Author zhushanshuo
 * @Date 2024-08-07 10:51:15
 */
@Slf4j
@Aspect
@Component
public class LockAspect {

	private static final Set<String> LOCK_KEY_SET = new ConcurrentHashSet<>();

	/**
	 * 是否初始化分布式锁前置信息的标识
	 */
	private static final AtomicBoolean alreadyInit = new AtomicBoolean(false);

	/**
	 * WatchDog 线程定义
	 */
	private static Thread thread = null;

	/**
	 * Lock 的实现类（Redis等）Map
	 */
	private static Map<String, Lock> LOCK_MAP;

	/**
	 * 初始化 Lock实现类
	 */
	@PostConstruct
	public void initLockPlugin() {
		// 新启线程，休眠100秒，不占用启动资源
		new Thread(() -> {
			try {
				TimeUnit.MILLISECONDS.sleep(100000l);
			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			}
			lazyInitOnce();
		}).start();

	}

	/**
	 * 懒加载（使用 synchronized 双重检查锁机制）
	 * 1、加载锁前置信息
	 * 2、初始化WatchDog Timer
	 */
	public void lazyInitOnce() {
		if (!alreadyInit.get()) {
			synchronized (LockAspect.class) {
				if (!alreadyInit.get()) {
					alreadyInit.set(true);
					// Lock接口的子实现类插件填充MAP
					LOCK_MAP =  SpringUtil.getBeanFactory().getBeansOfType(Lock.class);
					log.info("LOCK_MAP init, {}", JsonUtil.toJson(LOCK_MAP.size()));
					RedisProcessor redisProcessor = SpringUtil.getBean(RedisProcessor.class);
					// 获取 Redis ， 处理 WatchDog
					if (ObjectUtils.isEmpty(redisProcessor)) {
						return;
					}
					if (ObjectUtils.isEmpty(thread)) {
						// 初始化 WatchDog 线程
						thread = new Thread(() -> {
							Timer timer = new WatchDogTask(() -> {
								LOCK_KEY_SET.stream().forEach(item -> redisProcessor.watch(item));
							});
							// WatchDog 机制，每隔10秒，对redis锁续命运
							while (true) {
								timer.start();
								try {
									TimeUnit.MILLISECONDS.sleep(LockExpireEnum.MILL_30000.mill / 3l);
								} catch (InterruptedException e) {
									e.printStackTrace();
								}
							}
						});
						// 设置成守护线程，只要用户线程在，则一直活跃处理锁续命
						thread.setDaemon(true);
						thread.start();
					}
				}
			}
		}
	}

	/**
	 * 切入点：
	 * 即 添加了  LockEnable 自定义锁注解的方法切入点
	 */
	@Pointcut("@annotation(com.zhuss.lock.LockEnable)")
	public void pointCut() {

	}

	/**
	 * 在切入点目标体方法执行之前，需要做前置处理，即：加锁处理
	 * @param point  切入点主体
	 * @throws Throwable
	 */
	@Before("pointCut()")
	public void before(JoinPoint point) throws Throwable {
		// 拿到方法签名
		Signature sign = point.getSignature();
		if (sign instanceof MethodSignature) {
			// 初始化 锁注解，自定义参数信息：如，锁组件、所属系统、所属模块、对应的操作、目标记录业务ID（如：当互斥修改时，记录业务主键）
			LockItem lockItem =  init(point);
			// 锁信息为空，跳过加锁
			if (ObjectUtils.isEmpty(lockItem)) {
				log.info("加锁失败, 锁信息为空:");
				throw new AabException(ExceptionCode.加锁失败);
			}

			// 懒加载，避免系统刚刚启动时，并未执行初始化
			lazyInitOnce();

			// 获取锁对应的锁组件，如：redis
			Lock lock = LOCK_MAP.get(lockItem.getLockPlugin());
			// 按照注解中自定义锁参数，生成对应的key
			String key = LockKeyUtil.genKey(lockItem);
			// 获取 虚拟的分布式线程ID
			CurrentThreadIdGenerator bean = SpringUtil.getBean(CurrentThreadIdGenerator.class);
			Long clusterThreadId = bean.gen(LockKeyUtil.加锁获取线程);
			// 加锁，对应的Lock接口实现类去处理
			Boolean successful = lock.distributedLock(key, clusterThreadId, lockItem.getLockTime(), lockItem.getTimeoutMill());
			log.info("线程ID：{}, 加锁，key: {}, 加锁时间： {}, 加锁超时时间： {}, successful: {}, time: {}", clusterThreadId, key, lockItem.getLockTime(), lockItem.getTimeoutMill(), successful, System.currentTimeMillis());
			if (!successful) {
				log.info("线程ID：{}, 加锁失败, key: {}", clusterThreadId, key);
				// 加锁失败时，需要清空当前线程锁信息，help map gc
				// 一般只会在可重入锁的第一次加锁失败，因为涉及到锁key竞争
				bean.clearThreadId();
				throw new AabException(ExceptionCode.加锁失败);
			}
			// 收集所有加锁成功的key
			if (!LOCK_KEY_SET.contains(key)) {
				LOCK_KEY_SET.add(key);
			}

		}
	}

	/**
	 * 初始化 锁注解自定义参数信息：
	 * 包含 锁组件、所属系统、所属模块、对应的操作、目标记录业务ID（如：当互斥修改时，记录业务主键）
	 *
	 * @param point
	 * @return
	 */
	private LockItem init(JoinPoint point) {
		Signature sign = point.getSignature();
		Method method = ((MethodSignature) sign).getMethod();
		LockEnable lockAnno = method.getAnnotation(LockEnable.class);
		// 锁组件
		String lockPlugin = lockAnno.lockPlugin().name();
		// 所属系统
		String system = lockAnno.system().name();
		// 所属模块
		String module = lockAnno.module().name();
		// 对应的操作
		String action = lockAnno.action().name();
		// 加锁时间
		Long lockTime = lockAnno.lockTime().mill;
		// 超时时间
		Long timeoutMill = lockAnno.timeoutMill().mill;
		// 获取对应的 切入点方法 参数
		Object[] args = point.getArgs();
		// 如果是修改某一条记录，记录的主键ID可作为key的生成条件
		String servicePkId;
		// 如果依赖某条数据修改，切入点的业务方法参数需作为LockBo的子类（extends LockBo）
		List<? extends Object> objectList = Arrays.stream(args).filter(item -> item instanceof LockBo).collect(Collectors.toList());
		if (CollectionUtils.isEmpty(objectList) || objectList.size() > 1) {
			// 没有业务主键ID，跳过
			servicePkId = null;
		} else {
			// 获取业务主键ID
			LockBo bo = (LockBo) objectList.get(0);
			servicePkId = bo.getServicePkId();
		}

		// 返回锁 参数体
		return new LockItem(lockPlugin, system, module, action, lockTime, timeoutMill, servicePkId);
	}

	/**
	 * 在切入点目标体方法执行之后，需要做后置处理，即：释放锁处理
	 * @param point
	 * @throws Throwable
	 */
	@After("pointCut()")
	public void after(JoinPoint point) throws Throwable {
		Signature sign = point.getSignature();
		if (sign instanceof MethodSignature) {
			LockItem lockItem =  init(point);
			if (ObjectUtils.isEmpty(lockItem)) {
				return;
			}
			Lock lock = LOCK_MAP.get(lockItem.getLockPlugin());
			String key = LockKeyUtil.genKey(lockItem);
			// 获取 虚拟的分布式线程ID
			CurrentThreadIdGenerator bean = SpringUtil.getBean(CurrentThreadIdGenerator.class);
			Long clusterThreadId = bean.gen(LockKeyUtil.释放锁获取线程);
			Boolean unlocked = lock.unlock(key, clusterThreadId);
			log.info("线程ID：{}, 释放锁， key: {}, 加锁时间： {}, 加锁超时时间： {}, unlocked: {}, time: {}", clusterThreadId, key, lockItem.getLockTime(), lockItem.getTimeoutMill(), unlocked, System.currentTimeMillis());
			// 如果没有锁占用，即所有的锁都已经释放了，则WatchDog不需要续命， 避免死锁
			if (!bean.hasLock()) {
				LOCK_KEY_SET.remove(key);
			}
			if (!unlocked) {
				log.info("线程ID：{}, 释放失败, key: {}", clusterThreadId, key);
				// 释放锁失败，则WatchDog也不需要续命，避免死锁
				LOCK_KEY_SET.remove(key);
				throw new AabException(ExceptionCode.加锁失败);
			}
		}
	}

}

